package miage.rest.resource.candidature;

import com.thoughtworks.xstream.XStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.Date;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import miage.rest.db.Database;
import miage.rest.model.Candidat;
import miage.rest.model.Candidature;
import miage.rest.model.SessionDiplome;
import miage.rest.xml.XstreamRepresentation;
import miage.rest.xml.xstream.converter.HrefConverter;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.Get;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import miage.rest.routing.UriTemplateFinder;
import miage.rest.form.Form;
import miage.rest.mail.EmailHeaders;
import miage.rest.resource.ResourceError;
import miage.rest.resource.mail.MailClientResourceFactory;
import miage.rest.validation.DateValidator;
import miage.rest.validation.LengthValidator;
import miage.rest.validation.LongRangeValidator;
import miage.rest.validation.NotEmptyValidator;
import miage.rest.validation.RegexValidator;
import miage.rest.validation.Validator;
import org.apache.commons.dbutils.DbUtils;
import miage.rest.mail.Email;
import miage.rest.security.PasswordGenerator;
import miage.rest.security.Roles;
import org.restlet.data.CharacterSet;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.Post;
import org.restlet.util.WrapperRepresentation;

/**
 * Resource candidatures
 *
 * @author marya
 */
public class CandidaturesResource extends ServerResource {

	private final Database database;
	private UriTemplateFinder uriTemplateFinder;
	private MailClientResourceFactory mailFactory;
	private SessionDiplome session;

	public CandidaturesResource(Database database,
								UriTemplateFinder uriTemplateFinder,
								MailClientResourceFactory mailClientResourceFactory) {

		this.database = database;
		this.uriTemplateFinder = uriTemplateFinder;
		this.mailFactory = mailClientResourceFactory;
	}

	/** Initialisation de la ressource a partir de la chaine d'url */
	@Override
	protected void doInit() throws ResourceException {

		final String sqlSession = "SELECT id_session FROM session_diplome WHERE id_session = ?";

		Long idSession = null;
		String idSessionAttribute = getRequest().getAttributes().get("idSession").toString();
		LongRangeValidator longRangeValidator = new LongRangeValidator(1l, null);

		//Validation de l'id de la session
		if (longRangeValidator.isValid(idSessionAttribute)) {
			idSession = Long.parseLong(idSessionAttribute);
		} else {
			getResponse().setEntity(ResourceError.getNewRepresentation("invalidSessionId"));
			throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST);
		}

		//Vérification d'existence de la session
		try {
			session = database.fetchRow(sqlSession, SessionDiplome.class, idSession);
			if (session == null) {
				setExisting(false);
			}
		} catch (SQLException ex) {
			throw new ResourceException(Status.SERVER_ERROR_INTERNAL, ex);
		}

	}

	/**
	 * Représentation XML en réponse à une requête GET.
	 * Jeu de caractères UTF-8
	 */
	@Get
	public Representation headOrGet() throws Exception {
		Representation response = null;

		if (!isInRole(Roles.ADMIN.getName())) {
			setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
		}

		if (!getStatus().isError()) {
			/**
			 * Restlet ne permet pas de gérer la méthode HEAD
			 */
			if (getRequest().getMethod().equals(Method.HEAD)) {
				response = doHead();
			} else if (getRequest().getMethod().equals(Method.GET)) {
				response = doGet();
			}
		}

		return response;
	}

	/**
	 * Traitement de la requête HTTP HEAD
	 * @return La date de dernière modification des candidatures
	 */
	public Representation doHead() throws Exception {
		Representation response = null;
		String sqlLastAccess = "SELECT c.date_effet FROM candidature AS c WHERE c.date_effet <> '' AND c.id_session = " + session.getIdSession() + " ORDER BY c.date_effet DESC LIMIT 0, 1";
		Date lastAccessDate = (Date) database.fetchOne(sqlLastAccess);

		if (lastAccessDate != null) {
			WrapperRepresentation wr = new WrapperRepresentation(new EmptyRepresentation());
			wr.setModificationDate(new java.util.Date(lastAccessDate.getTime()));

			response = wr;
			setStatus(Status.SUCCESS_OK);
		} else {
			setStatus(Status.CLIENT_ERROR_NOT_FOUND);
			response = ResourceError.getNewRepresentation("Candidatures non trouvées pour cette session");
		}

		return response;
	}

	/** Reponse a une requete GET */
	public Representation doGet() throws Exception {
		Representation response;
		List<Candidature> candidatures = null;

		String sqlSessionDiplome = "SELECT id_candidature,id_candidat,id_statut FROM candidature WHERE id_session = " + session.getIdSession();
		candidatures = database.fetchAll(sqlSessionDiplome, Candidature.class);

		if (candidatures != null) {
			/**
			 * Pour chaque candidature dans la liste des SessionDiplome de la session
			 * on ne peut initialiser que idCandidat
			 * Il faut donc qu'on mette à jour l'attribut candidat de chaque candidature
			 */
			Iterator it = candidatures.iterator();
			while (it.hasNext()) {
				Candidature c = (Candidature) it.next();
				c.setCandidat(new Candidat(c.getIdCandidat()));
			}
			session.setCandidatures(candidatures);

			//Création d'une représentation XStream pour chaque candidature
			response = new XstreamRepresentation<SessionDiplome>(session);
			XStream xstream = ((XstreamRepresentation) response).getXstream();

			//Ajout de l'intégration des liens href  pour la classe Candidature
			//par l'intermédiaire de la classe HrefConverter
			xstream.registerConverter(
					new HrefConverter(
					Candidature.class,
					xstream.getConverterLookup(),
					uriTemplateFinder.find(CandidatureResource.class)));

			//Ajout de l'intégration des liens href  pour la classe Candidat
			//par l'intermédiaire de la classe HrefConverter
			xstream.registerConverter(
					new HrefConverter(
					Candidat.class,
					xstream.getConverterLookup(),
					uriTemplateFinder.find(CandidatResource.class)));

		} else {
			setStatus(Status.CLIENT_ERROR_NOT_FOUND);
			response = ResourceError.getNewRepresentation(
					"Candidatures non trouvées pour cette session");
		}
		return response;
	}


	@Post
	public Representation doPost(Representation entity) throws Exception {

		final String sqlCandidat = "INSERT INTO candidat("
						+ "nom,prenom,email,adresse,code_postal,ville,lieu_naissance,nom_marital,tel1,tel2,date_naissance,id_nationalite,sexe,password) "
						+ " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

		final String sqlCandidature = "INSERT INTO candidature(id_candidat,id_session,id_statut,commentaire,date_effet)"
							+ " VALUES(?,?,?,?,NOW())";


		Representation result = null;
		Connection connexion = null;
		List<ResourceError> errors = new ArrayList<ResourceError>();

		try {

			Form form = getNewCandidatureForm(entity);
			if (!form.isValid()) {

				for (String error : form.getMessages()) {
					errors.add(new ResourceError(error));
				}
				setStatus(Status.CLIENT_ERROR_BAD_REQUEST);

			} else {
				//Première candidature donc création d'un candidat
				String nom = form.getFirstValue("nom");
				String prenom = form.getFirstValue("prenom");
				String tel1 = form.getFirstValue("tel1");
				String tel2 = form.getFirstValue("tel2");
				String email = form.getFirstValue("email");
				String commentaire = form.getFirstValue("commentaire");
				DateFormat date = new SimpleDateFormat("d/M/y");
				Date date_naissance = new Date(date.parse(form.getFirstValue("dateNaissance")).getTime());
				String lieuNaissance = form.getFirstValue("lieuNaissance");
				String nomMarital = form.getFirstValue("nomMarital");
				String adresse = form.getFirstValue("adresse");
				String codePostal = form.getFirstValue("codePostal");
				String ville = form.getFirstValue("ville");
				String idNationalite = form.getFirstValue("idNationalite");
				String sexe = form.getFirstValue("sexe");
				String password = generatePassword();

				connexion = database.getNewConnection();
				connexion.setAutoCommit(false);

				Integer insertedCandidat = database.update(connexion,
														   sqlCandidat,
														   nom,
														   prenom,
														   email,
														   adresse,
														   codePostal,
														   ville,
														   lieuNaissance,
														   nomMarital,
														   tel1,
														   tel2,
														   date_naissance,
														   idNationalite,
														   sexe,
														   password).getGeneratedKeys();

				if (insertedCandidat == null || insertedCandidat < 1) {

					errors.add(new ResourceError("Impossible de créer le candidat"));
					setStatus(Status.SERVER_ERROR_INTERNAL);

				} else {
					Integer insertedCandidature = database.update(connexion,
																  sqlCandidature,
																  insertedCandidat,
																  session.getIdSession(),
																  4, commentaire).getGeneratedKeys();

					if (insertedCandidature == null || insertedCandidature < 1) {

						errors.add(new ResourceError("Impossible de créer le candidature"));
						setStatus(Status.SERVER_ERROR_INTERNAL);

					} else {

						result = new XstreamRepresentation<Candidature>(new Candidature(insertedCandidature));

						XStream xstream = ((XstreamRepresentation) result).getXstream();
						xstream.registerConverter(
								new HrefConverter(
								Candidature.class,
								xstream.getConverterLookup(),
								uriTemplateFinder.find(CandidatureResource.class)));

						sendMail(nom, prenom, password, email);
						connexion.commit();

						setStatus(Status.SUCCESS_CREATED);
					}
				}
			}

		} catch (Exception exc) {

			DbUtils.rollbackAndCloseQuietly(connexion);

			if (exc instanceof NumberFormatException) {
				setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
			} else {
				throw exc;
			}
		}

		if (!errors.isEmpty()) {
			result = ResourceError.getNewRepresentation(errors);
		}

		return result;
	}


	@SuppressWarnings("unchecked")
	private Form getNewCandidatureForm(Representation entity) {

		Form form = new Form(entity);
		Validator<String> notEmptyValidator = new NotEmptyValidator();
		Validator<String> lentghValidator100 = new LengthValidator(100);
		Validator<String> lentghValidator50 = new LengthValidator(50);
		Validator<String> namesValidator = new RegexValidator(
				"^([a-zA-Z'àâéèêëîïôùûçÀÂÉÈÔÙÛÇ\\s-]{1,100})$");
		Validator<String> emailValidator = new RegexValidator(
				"^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$");
		Validator<String> dateValidator = new DateValidator("d/m/y",
															"jj/mm/aaaa");
		//Récupération du nom, prénom et ajout de validateurs pour tester si le nom est vide et s'il valide une regex
		form.addValidators("nom", notEmptyValidator, namesValidator);
		form.addValidators("prenom", notEmptyValidator, namesValidator);
		form.addValidator("tel1", lentghValidator50);
		form.addValidator("tel2", lentghValidator50);
		form.addValidators("email", notEmptyValidator, lentghValidator50,
						   emailValidator);
		form.addValidators("dateNaissance", notEmptyValidator, dateValidator);
		form.addValidator("lieuNaissance", lentghValidator100);
		form.addValidator("nomMarital", lentghValidator100);
		form.addValidator("adresse", notEmptyValidator);
		form.addValidators("codePostal", notEmptyValidator, new LengthValidator(
				10));
		form.addValidators("ville", notEmptyValidator, lentghValidator50);
		form.addValidator("idNationalite", new LengthValidator(5));
		form.addValidator("sexe", new LengthValidator(20));
		form.addValidator("commentaire", new LengthValidator(250));

		return form;
	}

	/**
	 * Méthode pour envoyer un email en style REST :)
	 *
	 * @param to l'adresse de déstination
	 * @throws IOException
	 */
	private void sendMail(String nom, String prenom, String password, String to) {

		Email email = new Email();

		EmailHeaders emailHeaders = new EmailHeaders();
		emailHeaders.setFrom("test@miage.rest");
		emailHeaders.setSubject("Candidature à la formation MIAGE de Paris Descartes");
		emailHeaders.setTo(to);

		email.setBody("Bonjour " + nom + " " + prenom + " \n, Votre identifiant : " + to + "\n Votre mot de passe : " + password);
		email.setHeaders(emailHeaders);

		XstreamRepresentation<Email> representation  = new XstreamRepresentation<Email>(email);

		ClientResource mailClient = mailFactory.create();
		try {
			mailClient.post(representation.getText(),
							MediaType.TEXT_XML);
		} catch (Exception ex) {
			Logger.getLogger(CandidaturesResource.class.getName()).log(Level.SEVERE,
																	   null, ex);
		}

		if (mailClient.getStatus().isError()) {
			this.getLogger().log(Level.WARNING, "Impossible d'envoyer le mail");
		}

	}

	/**
	 * Génère un mot de passe pour l'étudiant
	 * @return
	 */
	private String generatePassword() throws Exception {
		PasswordGenerator generator = new PasswordGenerator();
		return generator.generate(PasswordGenerator.PRINTABLE_CHARACTERS);
	}

}
